package probability;

import java.util.ArrayList;

import basics.DenseVector;
import basics.Vector;

public class Indicator extends ProbabilityDistribution {

	@Override
	public double measure(Vector x) {
		return 0;
	}

	public static double computeVGreater(double t, double epsilon) {
		double q = GaussianDistribution.Q(t - epsilon, 0., 1.);
		double n = new GaussianDistribution(0., 1.).measure(new DenseVector(new double[] { t - epsilon }));
		return n / q;
	}

	public static double computeWGreater(double t, double epsilon) {
		return computeVGreater(t, epsilon) * (computeVGreater(t, epsilon) + (t - epsilon));
	}

	public static double computeVAbsolute(double t, double epsilon) {
		double ql = GaussianDistribution.Q(-epsilon - t, 0., 1.);
		double qu = GaussianDistribution.Q(epsilon - t, 0., 1.);
		GaussianDistribution g = new GaussianDistribution(0., 1.);
		double nl = g.measure(new DenseVector(new double[] { -epsilon - t }));
		double nu = g.measure(new DenseVector(new double[] { epsilon - t }));

		return (nl - nu) / (qu - ql);
	}

	public static double computeWAbsolute(double t, double epsilon) {
		double ql = GaussianDistribution.Q(-epsilon - t, 0., 1.);
		double qu = GaussianDistribution.Q(epsilon - t, 0., 1.);
		GaussianDistribution g = new GaussianDistribution(0., 1.);

		return Math.pow(computeVGreater(t, epsilon), 2)
				+ ((epsilon - t) * g.measure(new DenseVector(new double[] { epsilon - t })) + (t - epsilon) + (epsilon + t)
						* g.measure(new DenseVector(new double[] { epsilon + t }))) / (qu - ql);

	}

	public enum TruncatedGaussianType {
		xGreaterE, AbsxLessE
	}

	public static double computePi(double c, double d, double epsilon, TruncatedGaussianType type) {
		double w = 0;
		if (type == TruncatedGaussianType.xGreaterE) {
			w = computeWGreater(d / Math.sqrt(c), epsilon * Math.sqrt(c));
		} else if (type == TruncatedGaussianType.AbsxLessE) {
			w = computeWAbsolute(d / Math.sqrt(c), epsilon * Math.sqrt(c));
		}

		return c / (1. - w);
	}

	public static double computeTaw(double c, double d, double epsilon, TruncatedGaussianType type) {
		double v = 0, w = 0;
		if (type == TruncatedGaussianType.xGreaterE) {
			w = computeWGreater(d / Math.sqrt(c), epsilon * Math.sqrt(c));
			v = computeVGreater(d / Math.sqrt(c), epsilon * Math.sqrt(c));
		} else if (type == TruncatedGaussianType.AbsxLessE) {
			w = computeWAbsolute(d / Math.sqrt(c), epsilon * Math.sqrt(c));
			v = computeVAbsolute(d / Math.sqrt(c), epsilon * Math.sqrt(c));
		}

		return (d + Math.sqrt(c) * v) / (1. - w);
	}

	public static GaussianDistribution trueSkillBackwardEqualSum(int i, ArrayList<GaussianDistribution> vTof,
			ArrayList<GaussianDistribution> fTov, GaussianDistribution xTof, GaussianDistribution fTox) {
		// since all the coefficients are 1, then the formula is simplified :D
		double[] coeff = new double[vTof.size()];
		ArrayList<GaussianDistribution> fTovClone = (ArrayList<GaussianDistribution>) fTov.clone();
		ArrayList<GaussianDistribution> dist = (ArrayList<GaussianDistribution>) vTof.clone();

		for (int k = 0; k < dist.size() - 1; k++) {
			// GaussianDistribution.product(dist.get(k), fTov.get(k));

			coeff[k] = -1.;
		}

		coeff[dist.size() - 1] = 1;
		coeff[dist.size() - 1] = 1;
		fTovClone.remove(i);
		fTovClone.add(fTox);
		dist.remove(i);
		// dist.add(GaussianDistribution.product(xTof, fTox));
		dist.add(xTof);
		GaussianDistribution g = resolveIndicator(dist, coeff); // , fTovClone
		g.setMu(g.getMu() / vTof.get(i).getMu());
		g.setSigma2(g.getSigma2() / (vTof.get(i).getMu() * vTof.get(i).getMu()));

		return g;
	}

	public static GaussianDistribution trueSkillForwardEqualSum(ArrayList<GaussianDistribution> vTof,
			ArrayList<GaussianDistribution> fTov) {
		double[] coeff = new double[vTof.size()];
		ArrayList<GaussianDistribution> dist = (ArrayList<GaussianDistribution>) vTof.clone();

		for (int k = 0; k < vTof.size(); k++) {
			// GaussianDistribution.product(dist.get(k), fTov.get(k));
			coeff[k] = 1.;
		}

		return resolveIndicator(dist, coeff); // resolveIndicator
	}

	public static GaussianDistribution resolveIndicator(ArrayList<GaussianDistribution> vTof, double[] coeff) {
		// return resolveIndicatorTrueskill(vTof, coeff);
		double m = 0, v = 0;
		for (int k = 0; k < coeff.length; k++) {
			m += (coeff[k] * vTof.get(k).getMu());
			v += (coeff[k] * coeff[k] * vTof.get(k).getSigma2());
		}
		// v = v / (double) coeff.length - 1.;
		GaussianDistribution g = new GaussianDistribution(m, v);

		return g;
	}

	public static GaussianDistribution resolveIndicatorTrueskill(ArrayList<GaussianDistribution> vTof, double[] coeff) {
		// ArrayList<GaussianDistribution> dist,
		// ArrayList<GaussianDistribution> fTov,

		double pi = 0, taw = 0;
		for (int k = 0; k < coeff.length; k++) {
			// double piv = dist.get(k).getPi();
			// double pif = fTov.get(k).getPi();
			// double denom = (piv - pif != 0) ? (piv - pif) : 1E-2;

			double denom = vTof.get(k).getPi();
			pi += (coeff[k] * coeff[k] / denom);
			// double tawv = dist.get(k).getTaw();
			// double tawf = fTov.get(k).getTaw();
			double numer = vTof.get(k).getTaw();
			taw += (coeff[k] * (numer / denom)); // (tawv - tawf)
		}

		pi = 1. / pi;
		GaussianDistribution g = new GaussianDistribution();
		g.setPiTaw(pi, taw * pi);
		// g.setSigma2(g.getSigma2() / (double) coeff.length - 1.);
		return g;
	}

	@Override
	protected ProbabilityDistribution newInstance() {
		return new Indicator();
	}

	@Override
	public ProbabilityDistribution times(ProbabilityDistribution distribution) {
		return null;
	}
}